TS 的型別推論雖然很聰明,但在以下情境會「卡住」:
ts
CopyEdit
type ApiResult = User | ApiError;
function handle(result: ApiResult) {
// result 可能是 User,也可能是 ApiError
console.log(result.name); // ❌ TS 報錯:屬性 name 不存在於 ApiError
}
我們需要讓 TS 明白:「這個分支一定是某種型別」,
這就是型別守衛(Type Guard)的用途。
typeof
型別守衛ts
CopyEdit
function printId(id: string | number) {
if (typeof id === "string") {
console.log(id.toUpperCase()); // 這裡 TS 知道是 string
} else {
console.log(id.toFixed(2)); // 這裡 TS 知道是 number
}
}
適用於:string | number | boolean | bigint | symbol | undefined | object
這些基本型別判斷。
instanceof
型別守衛ts
CopyEdit
class AppError extends Error {
constructor(public code: string) {
super(code);
}
}
function handleError(err: unknown) {
if (err instanceof AppError) {
console.log(err.code); // 這裡 TS 知道 err 是 AppError
}
}
適用於:檢查物件是否來自某個 class。
in
運算子型別守衛ts
CopyEdit
type User = { id: string; name: string };
type ApiError = { code: string; message: string };
function handle(result: User | ApiError) {
if ("name" in result) {
console.log(result.name); // TS 知道是 User
} else {
console.log(result.code); // TS 知道是 ApiError
}
}
適用於:物件聯合型別,透過屬性判斷分支。
用 x is SomeType
告訴 TS 在分支內的型別:
ts
CopyEdit
function isUser(obj: any): obj is User {
return obj && typeof obj.id === "string" && typeof obj.name === "string";
}
function handle(result: User | ApiError) {
if (isUser(result)) {
console.log(result.name); // User
} else {
console.log(result.code); // ApiError
}
}
好處:
這是 TS 型別窄化的黃金模式,透過 共同屬性 + 字面量型別 讓 TS 自動分支。
ts
CopyEdit
type Success = { status: "success"; data: User };
type Fail = { status: "error"; error: ApiError };
type ApiResponse = Success | Fail;
function processResponse(res: ApiResponse) {
if (res.status === "success") {
console.log(res.data.name); // TS 自動知道是 Success
} else {
console.log(res.error.message); // TS 自動知道是 Fail
}
}
好處:
假設我們有 Result<T, E>
型別:
ts
CopyEdit
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E };
function isOk<T, E>(res: Result<T, E>): res is { ok: true; value: T } {
return res.ok;
}
function handleResult(res: Result<User, ApiError>) {
if (isOk(res)) {
console.log(res.value.name); // User
} else {
console.error(res.error.code); // ApiError
}
}
這樣就不用在 if
裡面重複寫判斷條件。
zod
的型別守衛ts
CopyEdit
import { z } from "zod";
const userSchema = z.object({
id: z.string(),
name: z.string(),
});
type User = z.infer<typeof userSchema>;
function isUser(data: unknown): data is User {
return userSchema.safeParse(data).success;
}
好處:型別判斷與資料驗證合一。
obj is Type
boolean
,那樣 TS 不會自動窄化。